/*
作者: soy
日期: 2025/10/11
------------------------------------------------------------


*/
/**
 * 初始化 CryptoJS 模块
 * @returns {object} CryptoJS 模块实例
 * @throws {Error} 如果 crypto-js 模块未安装
 */
const CryptoJS = (() => {
  try {
    return require("crypto-js");
  } catch (error) {
    throw new Error("\n缺少依赖：请安装 `crypto-js` 安装依赖\n");
  }
})();
/**
 * 初始化 axios 模块
 * @returns {object} axios 模块实例
 * @throws {Error} 如果 axios 模块未安装
 */
const axios = (() => {
  try {
    return require("axios");
  } catch (error) {
    throw new Error("\n缺少依赖：请安装 `axios` 安装依赖\n");
  }
})();
/**
 * 初始化 node-rsa 模块
 * @returns {object} node-rsa 模块实例
 * @throws {Error} 如果 node-rsa 模块未安装
 */
const NodeRSA = (() => {
  try {
    return require("node-rsa");
  } catch (error) {
    throw new Error("\n缺少依赖：请安装 `node-rsa` 安装依赖\n");
  }
})();
/**
 * 初始化 https 模块
 * @returns {object} https 模块实例
 * @throws {Error} 如果 https 模块未安装
 */
const https = (() => {
  try {
    return require("https");
  } catch (error) {
    throw new Error("\n缺少依赖：请安装 `https` 安装依赖\n");
  }
})();
const os = (() => {
  try {
    return require("os");
  } catch (error) {
    throw new Error("\n缺少依赖：请安装 `os` 安装依赖\n");
  }
})();
//=================================================================================
//=================================================================================
const fs = require('fs').promises;
const path = require('path');
const localFile = path.resolve(__dirname, "soy_api.js");
const REPO_URL = process.env.API_JS_URL || "https://raw.gitcode.com/soyyun/update/raw/main/soy_api.js";
let api = ''
//=================================================================================
//=================================================================================
//=================================================================================
//公共变量专区
//code地址
let wx_url = ''
//微信id
let wx_id = ''
//微信appid
let wx_appid = ''
//获取手机号加密地址
let wx_phone = ''
//授权的Authorization值
let wx_token = ''
//IP代理
let IP_agent = ''
//微信协议类型
let wx_type = ''
//微信云函数
let wx_cloud = ''


//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 随机延时函数
 * @param {number} min - 最小延时时间（秒）
 * @param {number} max - 最大延时时间（秒）
 * @returns {Promise<void>} 延时完成的 Promise
 * @throws {Error} 如果 min 或 max 不是正数，或 min > max
 */
async function Sleep_time(min, max) {
  if (
    typeof min !== "number" ||
    typeof max !== "number" ||
    min <= 0 ||
    max <= 0
  ) {
    throw new Error("min 和 max 必须是正数");
  }
  if (min > max) {
    throw new Error("min 必须小于或等于 max");
  }
  await wait(Math.floor(Math.random() * (max - min + 1)) + min);
}
//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 延时函数
 * @param {number} t - 延时时间（秒）
 * @returns {Promise<void>} 延时完成的 Promise
 * @throws {Error} 如果 t 不是正数
 */
async function wait(t) {
  if (typeof t !== "number" || t <= 0) {
    throw new Error("t 必须是正数");
  }
  return new Promise((resolve) => setTimeout(resolve, t * 1000));
}
//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 对手机号进行脱敏处理
 * @param {string} phone_num - 手机号
 * @returns {string} 脱敏后的手机号
 * @throws {Error} 如果 phone_num 不是字符串
 */
function phoneNum(phone_num) {
  if (typeof phone_num !== "string") {
    throw new Error("phone_num 必须是字符串");
  }
  if (phone_num.length === 11) {
    return phone_num.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2");
  }
  return phone_num;
}
//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 对字符串进行补位操作
 * @param {string|number} num - 要补位的字符串或数字
 * @param {number} len - 补位后的目标长度
 * @param {string} [padding="0"] - 补位字符，默认为 "0"
 * @param {string} [side="左"] - 补位方向，可选 "左" 或 "右"，默认为 "左"
 * @returns {string} 补位后的字符串
 * @throws {Error} 如果 len 不是正整数或 side 不是 "左" 或 "右"
 */
function padStr(num, len, padding = "0", side = "左") {
  if (typeof len !== "number" || len <= 0 || !Number.isInteger(len)) {
    throw new Error("len 必须是正整数");
  }
  if (side !== "左" && side !== "右") {
    throw new Error('side 必须是 "左" 或 "右"');
  }
  const str = String(num);
  return side === "左" ? str.padStart(len, padding) : str.padEnd(len, padding);
}
//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 从数组中随机选择一个元素
 * @param {Array} a - 输入数组
 * @returns {*} 随机选择的数组元素
 * @throws {Error} 如果 a 不是数组或为空
 */
function randomList(a) {
  if (!Array.isArray(a) || a.length === 0) {
    throw new Error("输入字符串必须是非空数组");
  }
  const idx = Math.floor(Math.random() * a.length);
  return a[idx];
}
//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 生成指定长度的 MD5 哈希值（16 或 32 位）
 * @param {string} str - 待哈希的字符串
 * @param {number} [len=32] - 哈希长度（16 或 32）
 * @param {boolean} [upper=false] - 是否返回大写哈希值
 * @returns {string} MD5 哈希值
 * @throws {Error} 如果 str 不是字符串或 len 不是 16 或 32
 */
function MD5(str, len = 32, upper = false) {
    
  if (typeof str !== "string") {
    str = str.toString()
  }
  if (len !== 16 && len !== 32) {
    throw new Error("len 必须是 16 或 32");
  }
  
  let hash = CryptoJS.MD5(str).toString();
  if (len === 16) {
    hash = hash.slice(8, 24);
  }
  if (upper) {
    hash = hash.toUpperCase();
  }
  return hash;
}
//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 生成指定长度的随机字符串（包含字母和数字）
 * @param {number} n - 字符串长度
 * @returns {string} 随机字符串
 * @throws {Error} 如果 n 不是正整数
 */
function getStr(n, l = false) {
  if (typeof n !== "number" || n <= 0 || !Number.isInteger(n)) {
    throw new Error("n 必须是正整数");
  }
  const chars = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678";
  const result = Array.from(
    { length: n },
    () => chars[Math.floor(Math.random() * chars.length)]
  ).join("");
  if(l==true){
      return result;
  }else{
      return result.toLowerCase();
  }
  
}
//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 生成指定长度的随机数字字符串
 * @param {number} n - 字符串长度
 * @returns {string} 随机数字字符串
 * @throws {Error} 如果 n 不是正整数
 */
function getNum(n) {
  if (typeof n !== "number" || n <= 0 || !Number.isInteger(n)) {
    throw new Error("n 必须是正整数");
  }
  const chars = "1234567890";
  const result = Array.from(
    { length: n },
    () => chars[Math.floor(Math.random() * chars.length)]
  ).join("");
  return result;
}
//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 生成指定范围内的随机整数（包含 min 和 max）
 * @param {number} min - 最小值（包含）
 * @param {number} max - 最大值（包含）
 * @returns {number} 随机整数
 * @throws {Error} 如果 min > max 或输入非数值
 */
function getRandomInt(min, max) {
  if (typeof min !== "number" || typeof max !== "number") {
    throw new Error("min 和 max 必须是数值");
  }
  if (min > max) {
    throw new Error("min 必须小于或等于 max");
  }
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 使用 HmacSHA256 算法生成签名并返回指定格式的字符串
 * @param {string} msg - 待签名的消息
 * @param {string} Key - 签名密钥
 * @param {string} [format="hex"] - 返回格式，可选 "hex" 或 "base64"
 * @returns {string} 签名字符串（十六进制或 Base64 格式）
 * @throws {Error} 如果 msg 或 Key 不是字符串，或 format 不是 "hex" 或 "base64"
 */
function HmacSHA256(msg, Key, format = "hex") {
  if (typeof msg !== "string" || typeof Key !== "string") {
    throw new Error("msg 和 Key 必须是字符串");
  }
  if (format !== "hex" && format !== "base64") {
    throw new Error('format 必须是 "hex" 或 "base64"');
  }
  // 使用 HmacSHA256 算法生成签名
  const hash = CryptoJS.HmacSHA256(msg, Key);
  // 根据 format 返回对应格式的签名
  return format === "hex"
    ? hash.toString(CryptoJS.enc.Hex)
    : hash.toString(CryptoJS.enc.Base64);
}
//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 检查数字是否为偶数
 * @param {number} number - 待检查的数字
 * @returns {boolean} 如果是偶数返回 true，否则返回 false
 * @throws {Error} 如果 number 不是数字
 */
function checkOddEven(number) {
  if (typeof number !== "number") {
    throw new Error("number 必须是数字");
  }
  return number % 2 === 0;
}
//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 使用 DES 算法加密消息
 * @param {string} msg - 待加密的消息
 * @param {string} key - 加密密钥
 * @param {string} iv - 初始化向量（仅 CBC 模式需要）
 * @param {string} [mode="CBC"] - 加密模式，可选 "CBC" 或 "ECB"
 * @returns {string} 加密后的字符串
 * @throws {Error} 如果 msg 或 key 不是字符串，或 mode 不是 "CBC" 或 "ECB"，或 CBC 模式下 iv 不是字符串
 */
function DES_encryptBy(msg, key, iv, mode = "CBC") {
  if (typeof msg !== "string" || typeof key !== "string") {
    throw new Error("msg 和 key 必须是字符串");
  }
  if (mode !== "CBC" && mode !== "ECB") {
    throw new Error('mode 必须是 "CBC" 或 "ECB"');
  }
  const parsedKey = CryptoJS.enc.Utf8.parse('DbaCsNok');
  iv = CryptoJS.enc.Utf8.parse('12345678');
  const options = {
    padding: CryptoJS.pad.Pkcs7,
    mode: CryptoJS.mode.CBC,
    iv:iv
  };
  
    //options.iv = CryptoJS.enc.Utf8.parse('12345678');
  
  //console.log(options)
  const encrypted = CryptoJS.DES.encrypt('mi7758258', parsedKey, options);
  //console.log(msg, key, iv)
  return encrypted.toString();
}
//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 使用 DES 算法解密消息
 * @param {string} msg - 待解密的消息
 * @param {string} key - 解密密钥
 * @param {string} iv - 初始化向量（仅 CBC 模式需要）
 * @param {string} [mode="CBC"] - 解密模式，可选 "CBC" 或 "ECB"
 * @returns {string} 解密后的字符串
 * @throws {Error} 如果 msg 或 key 不是字符串，或 mode 不是 "CBC" 或 "ECB"，或 CBC 模式下 iv 不是字符串
 */
function DES_decryptBy(msg, key, iv, mode = "CBC") {
  if (typeof msg !== "string" || typeof key !== "string") {
    throw new Error("msg 和 key 必须是字符串");
  }
  if (mode !== "CBC" && mode !== "ECB") {
    throw new Error('mode 必须是 "CBC" 或 "ECB"');
  }
  const parsedKey = CryptoJS.enc.Base64.parse(key) || CryptoJS.enc.Utf8.parse(key);
  const options = {
    padding: CryptoJS.pad.Pkcs7,
    mode: mode === "CBC" ? CryptoJS.mode.CBC : CryptoJS.mode.ECB,
  };
  if (mode === "CBC") {
    if (typeof iv !== "string") {
      throw new Error("CBC 模式下 iv 必须是字符串");
    }
    options.iv = CryptoJS.enc.Base64.parse(iv) || CryptoJS.enc.Utf8.parse(iv);
  }
  const decrypted = CryptoJS.TripleDES.decrypt(msg, parsedKey, options);
  //console.log(decrypted.toString(CryptoJS.enc.Base64))
  return decrypted.toString(CryptoJS.enc.Base64);
}
//=================================================================================
//=================================================================================
//=================================================================================
/**不管 key / iv 是什么编码（Hex/Base64/UTF-8），自动找出第一组能成功加密的组合
 * 使用 AES 算法加密消息
 * @param {string} msg - 待加密的消息
 * @param {string} key - 加密密钥
 * @param {string} iv - 初始化向量（仅 CBC 模式需要）
 * @param {string} [mode="CBC"] - 加密模式，可选 "CBC" 或 "ECB"
 * @returns {string} 加密后的字符串
 * @throws {Error} 如果 msg 或 key 不是字符串，或 mode 不是 "CBC" 或 "ECB"
 */
function AES_encrypt(plainText, key, iv, mode = 'CBC', outEnc = 'base64') {
    //console.log(plainText, key, iv)
  const keyBytes = parseMaterial(key);
  const ivBytes  = parseMaterial(iv);

  const cipher = CryptoJS.AES.encrypt(plainText, keyBytes, {
    iv: ivBytes,
    mode: CryptoJS.mode[mode] || CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  });

  return outEnc === 'hex'
       ? cipher.ciphertext.toString(CryptoJS.enc.Hex)
       : cipher.toString();               // 默认 Base64
}
//=================================================================================
//=================================================================================
//=================================================================================

/**不管 key / iv 是什么编码（Hex/Base64/UTF-8），自动找出第一组能成功加密的组合
 * AES 解密函数
 * @param {string} encryptedText - 密文（Base64编码）
 * @param {string|Buffer} key - 密钥（支持多种格式）
 * @param {string|Buffer} iv - 初始向量（支持多种格式）
 * @param {string} mode - 加密模式，默认 CBC
 * @param {string} padding - 填充模式，默认 Pkcs7
 * @returns {string} 解密后的明文
 */
function AES_decrypt(encryptedText, key, iv, mode = 'CBC') {

    const PARSERS = {
        hex: s => CryptoJS.enc.Hex.parse(s),
        base64: s => CryptoJS.enc.Base64.parse(s),
        utf8: s => CryptoJS.enc.Utf8.parse(s)
    };

    // 只看解密结果是不是可见 ASCII
    const looksLikeText = str => str && /^[\x20-\x7E\r\n\t]*$/.test(str.slice(0, 100));

    const tryDecrypt = (keyBytes, ivBytes) => {
        try {
            const d = CryptoJS.AES.decrypt(encryptedText, keyBytes, {
                iv: ivBytes,
                mode: CryptoJS.mode[mode] || CryptoJS.mode.CBC,
                padding: CryptoJS.pad.Pkcs7
            });
            const plain = d.toString(CryptoJS.enc.Utf8);
            return looksLikeText(plain) ? plain : null;
        } catch { return null; }
    };

    // 穷举 3×3
    for (const [kName, kParse] of Object.entries(PARSERS)) {
        for (const [vName, vParse] of Object.entries(PARSERS)) {
            const keyBytes = kParse(key);
            const ivBytes  = vParse(iv);
            const plain = tryDecrypt(keyBytes, ivBytes);
            if (plain) {
                //console.log(`猜中：key=${kName}, iv=${vName}`);
                return plain;
            }
        }
    }
    throw new Error('解密无法识别 key/iv 编码，或密文/密钥错误');
}
//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 严格判断 str 属于 hex | base64 | utf8
 * 返回值只可能是这三种之一，不会返回 unknown
 * 逻辑：
 *   1. 长度合法 + 纯 hex 字符 → hex
 *   2. 长度合法 + 纯 base64 字符且能解码自洽 → base64
 *   3. 其余全部属于 utf8（JS 字符串本身即可看成 UTF-8 源）
 */
function detectEncoding(str) {
  if (!str || typeof str !== 'string') return 'utf8';

  const len = str.length;

  /* 1. Hex：长度 32/48/64 且纯十六进制 */
  if ([32,48,64].includes(len) && /^[0-9a-fA-F]+$/.test(str)) return 'hex';

  /* 2. Base64：必须含 + 或 / 或 =，且能自洽 */
  if (/[+/]/.test(str) || str.endsWith('=')) {
    if (len % 4 === 0 && /^[A-Za-z0-9+/]*={0,2}$/.test(str)) {
      try {
        if (Buffer.from(str, 'base64').toString('base64') === str) return 'base64';
      } catch (_) {}
    }
  }

  /* 3. 其余通通当 utf8 处理 */
  return 'utf8';
}
/* 统一入口 */
function parseMaterial(m) {
    //console.log(m)
  const enc = detectEncoding(m);
  
  if (enc === 'hex')return CryptoJS.enc.Hex.parse(m);
  if (enc === 'base64')return CryptoJS.enc.Base64.parse(m);
  return CryptoJS.enc.Utf8.parse(m);     // utf8
}
//=================================================================================
//=================================================================================
//=================================================================================
// 生成 UUID
function getUUID(length = 36) {
  const chars = "0123456789abcdef";
  const randomChar = () => chars.charAt(Math.floor(Math.random() * 16));
  const uuid = Array.from({ length }, (_, i) => randomChar());

  // 设置 UUID 的固定格式字符
  uuid[14] = "4";
  uuid[19] = chars.charAt((parseInt(uuid[19], 16) & 0x3) | 0x8);
  [8, 13, 18, 23].forEach((pos) => (uuid[pos] = "-"));

  return uuid.join("");
}
//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 生成指定长度的随机字符串，字符串来源于指定的句子
 * @param {number} len - 随机字符串的长度
 * @param {string} sentence - 用于生成随机字符的源字符串
 * @returns {string} 随机生成的字符串
 * @throws {Error} 如果 len 不是正整数或 sentence 不是字符串
 */
function get_random_sentence(len, sentence) {
  if (typeof len !== "number" || len <= 0 || !Number.isInteger(len)) {
    throw new Error("len 必须是正整数");
  }
  if (typeof sentence !== "string") {
    throw new Error("sentence 必须是字符串");
  }
  const characters = Array.from(sentence);
  const randomChars = Array.from({ length: len }, () => {
    const randomIndex = Math.floor(Math.random() * characters.length);
    return characters[randomIndex];
  });
  return randomChars.join("");
}
//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 将对象转换为 URL 查询字符串格式
 * @param {Object} e - 需要转换的对象
 * @returns {string} 转换后的查询字符串
 */
function getStrFun(e) {
  var t = "";
  for (var n in e) t += "".concat(n, "=").concat(e[n], "&");
  return t.lastIndexOf("&") == t.length - 1 ? t.substring(0, t.length - 1) : t;
}
//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 使用 RSA 公钥对指定字符串进行加密
 * @param {string} t - 需要加密的字符串
 * @param {string} key - RSA 公钥
 * @param {string} [outputFormat="hex"] - 输出格式，支持 "hex" 或 "base64"
 * @returns {string} 加密后的字符串（十六进制或 Base64 格式）
 */
function encrypt_rsa(t, key, outputFormat = "base64") {
    const nodersa = new NodeRSA(`-----BEGIN PUBLIC KEY-----\n${key}\n-----END PUBLIC KEY-----`);
    
    nodersa.setOptions({ encryptionScheme: 'pkcs1' });
    const encryptedText = nodersa.encrypt(t, 'base64');
    console.log('加密后的文本:', encryptedText);
    return encryptedText;
}
//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 使用 RSA 私钥对指定字符串进行解密
 * @param {string} encryptedText - 需要解密的字符串（Base64 或十六进制格式）
 * @param {string} privateKey - RSA 私钥
 * @param {string} [inputFormat="base64"] - 输入格式，支持 "base64" 或 "hex"
 * @returns {string} 解密后的原始字符串
 */
function decrypt_rsa(t, key, outputFormat = "base64") {
  const nodersa = new NodeRSA(
    "-----BEGIN PUBLIC KEY-----\n" + key + "\n-----END PUBLIC KEY-----"
  );
  nodersa.setOptions({ encryptionScheme: "pkcs1" });
  const decryptText = nodersa.decrypt(
    t,
    outputFormat === "base64" ? "base64" : "hex",
    "utf8"
  );
  return decryptText;
}
//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 发送 HTTP 请求
 * @param {string} m - HTTP 方法（get/post/put/delete）
 * @param {Object} t - 请求配置（url, headers, body, timeout）
 * @param {string} p - 代理配置（格式：IP:端口）
 * @param {Function} e - 回调函数（error, request, response）
 */
async function send(m, t, p, e = () => {}) {
  m = m.toLowerCase()
  // 校验 HTTP 方法
  const validMethods = new Set(["get", "post", "put", "delete"]);
  if (!validMethods.has(m)) {
    console.log(`无效的 HTTP 方法：${m}`);
    return;
  }
  
  // 动态设置请求头
  if (t.headers) {
    if (m === "get") {
      delete t.headers["content-type"];
      delete t.headers["content-length"];
    }
  }
  
  // 代理配置（仅在需要时生成）
  const proxyConfig = (p ?? '').includes(':') 
  ? {protocol:'http',host: p.split(':')[0],port: Number(p.split(':')[1]),headers:{'Connection':'keep-alive'}}   // 端口转数字更稳妥
  : undefined;

  // 请求配置
  const axiosConfig = {
    method: m,
    url: t.url,
    headers: t.headers,
    timeout: t.timeout,
    [m === 'get' ? 'params' : 'data']: m === "get" ? undefined : t.body,
    proxy: proxyConfig,
    httpsAgent: proxyConfig ? new https.Agent({rejectUnauthorized:false}):undefined,
    maxRedirects: 5,
    validateStatus: s => s < 400,
  };
  //console.log(axiosConfig)
  
  //console.log(res)
  try {
      const res = await axios(axiosConfig);
      //console.log(res.headers)
      if(res.status == 200){
          return {err: null, res: res, data: res.data };
          
      }else if(res.status == 301){
          return {err: null, res: res, data: res.data?res.data:'请求响应状态:'+res.status};
          
      }else{
          return {err: null, res: res, data: res.data?res.data:'请求响应状态:'+res.status};
          
          
      }
      
  } catch (err) {
      const status = err.response?.status || err.status || 'NETWORK_ERROR';
      const message = err.response?.data || err.message || null;
      return {
          err: status,
          res: err.response || null,
          data:message
      };
    }
}
//=================================================================================
//=================================================================================
//=================================================================================
// 获取代理 IP
async function fetchProxyIp(url) {
  try {
    const { data } = await axios.get(url,{timeout:5000});
    console.log(data)
    // 检查数据是否为空或非字符串
    if (typeof data !== "string" || !data.trim()) {
      console.log("获取代理 IP 无效:");
      return null;
    }
    // 检查格式是否为 IP:端口
    const parts = data.split(":");
    if (parts.length !== 2) {
      console.log("获取非代理 IP 格式:");
      return null;  
    }
    // 返回有效数据
    return data; 
  } catch {
    console.log("获取代理 IP 失败:");
    return null;
  }
}
//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 获取指定长度的时间戳
 * @param {number} len - 时间戳长度（10 或 13），默认为 13
 * @returns {number} 对应位数的时间戳
 * @throws {Error} 如果 len 不是 10 或 13
 */
function getTimestamps(len = 13) {
  if (len !== 10 && len !== 13) {
    throw new Error("参数 len 必须是 10 或 13");
  }
  const now = new Date();
  return len === 10 ? Math.floor(now.getTime() / 1000) : now.getTime();
}
//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 格式化日期时间
 * @param {Date|string|number} date - 日期对象、日期字符串或时间戳
 * @param {string} format - 格式字符串，支持以下占位符：
 *   YYYY: 4位年份
 *   YY: 2位年份
 *   MM: 月份(01-12)
 *   M: 月份(1-12)
 *   DD: 日期(01-31)
 *   D: 日期(1-31)
 *   HH: 24小时制小时(00-23)
 *   H: 24小时制小时(0-23)
 *   hh: 12小时制小时(01-12)
 *   h: 12小时制小时(1-12)
 *   mm: 分钟(00-59)
 *   m: 分钟(0-59)
 *   ss: 秒(00-59)
 *   s: 秒(0-59)
 *   SSS: 毫秒(000-999)
 *   A: 上午/下午
 *   a: am/pm
 * @returns {string} 格式化后的日期时间字符串
 * @example
 * formatDateTime(new Date(), 'YYYY-MM-DD HH:mm:ss') // 2023-10-11 15:30:45
 * formatDateTime(Date.now(), 'YY/MM/DD hh:mm:ss A') // 23/10/11 03:30:45 下午
 */
function formatDateTime(format = "YYYY-MM-DD HH:mm:ss") {
  let d = getTimestamps();
  if (!(d instanceof Date)) {
    d = new Date(d);
  }

  const pad = (n) => n.toString().padStart(2, "0");
  const pad3 = (n) => n.toString().padStart(3, "0");

  const year = d.getFullYear();
  const month = d.getMonth() + 1;
  const day = d.getDate();
  const hours = d.getHours();
  const minutes = d.getMinutes();
  const seconds = d.getSeconds();
  const milliseconds = d.getMilliseconds();
  const isAM = hours < 12;

  const replacements = {
    YYYY: year,
    YY: year.toString().slice(-2),
    MM: pad(month),
    M: month,
    DD: pad(day),
    D: day,
    HH: pad(hours),
    H: hours,
    hh: pad(hours % 12 || 12),
    h: hours % 12 || 12,
    mm: pad(minutes),
    m: minutes,
    ss: pad(seconds),
    s: seconds,
    SSS: pad3(milliseconds),
    A: isAM ? "上午" : "下午",
    a: isAM ? "am" : "pm",
  };

  return format.replace(
    /YYYY|YY|MM|M|DD|D|HH|H|hh|h|mm|m|ss|s|SSS|A|a/g,
    (match) => replacements[match]
  );
}

//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 3DES 加密
 * @param {string} plainText - 待加密的明文
 * @param {string} key - 加密密钥（16 或 24 字节）
 * @param {string} [iv] - 初始化向量（仅 CBC 模式需要）
 * @param {string} [mode="CBC"] - 加密模式（CBC 或 ECB）
 * @returns {string} - Base64 编码的密文
 */
function tripleDESEncrypt(plainText, key, iv, mode = "CBC") {
  // 输入验证
  if (typeof plainText !== "string" || !plainText.trim()) {
    throw new Error("plainText 必须是非空字符串");
  }
  if (typeof key !== "string" || !key.trim()) {
    throw new Error("key 必须是非空字符串");
  }
  if (mode !== "CBC" && mode !== "ECB") {
    throw new Error('mode 必须是 "CBC" 或 "ECB"');
  }
  if (mode === "CBC" && (typeof iv !== "string" || !iv.trim())) {
    throw new Error("iv 必须是非空字符串");
  }

  try {
    const parsedKey = CryptoJS.enc.Utf8.parse(key);
    const options = {
      mode: mode === "CBC" ? CryptoJS.mode.CBC : CryptoJS.mode.ECB,
      padding: CryptoJS.pad.Pkcs7,
    };
    if (mode === "CBC") {
      options.iv = CryptoJS.enc.Utf8.parse(iv);
    }

    const encrypted = CryptoJS.TripleDES.encrypt(
      CryptoJS.enc.Utf8.parse(plainText),
      parsedKey,
      options
    );
    return encrypted.toString();
  } catch (error) {
    console.error("3DES 加密失败:", error);
    throw new Error("加密过程中发生错误");
  }
}
//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 3DES 解密
 * @param {string} cipherText - Base64 编码的密文
 * @param {string} key - 解密密钥（16 或 24 字节）
 * @param {string} [iv] - 初始化向量（仅 CBC 模式需要）
 * @param {string} [mode="CBC"] - 解密模式（CBC 或 ECB）
 * @returns {string} - 解密后的明文
 */
function tripleDESDecrypt(cipherText, key, iv, mode = "CBC") {
  // 输入验证
  if (typeof cipherText !== "string" || !cipherText.trim()) {
    throw new Error("cipherText 必须是非空字符串");
  }
  if (typeof key !== "string" || !key.trim()) {
    throw new Error("key 必须是非空字符串");
  }
  if (mode !== "CBC" && mode !== "ECB") {
    throw new Error('mode 必须是 "CBC" 或 "ECB"');
  }
  if (mode === "CBC" && (typeof iv !== "string" || !iv.trim())) {
    throw new Error("iv 必须是非空字符串");
  }

  try {
    const parsedKey = CryptoJS.enc.Utf8.parse(key);
    const options = {
      mode: mode === "CBC" ? CryptoJS.mode.CBC : CryptoJS.mode.ECB,
      padding: CryptoJS.pad.Pkcs7,
    };
    if (mode === "CBC") {
      options.iv = CryptoJS.enc.Utf8.parse(iv);
    }

    const decrypted = CryptoJS.TripleDES.decrypt(
      cipherText,
      parsedKey,
      options
    );
    return decrypted.toString(CryptoJS.enc.Utf8);
  } catch (error) {
    console.error("3DES 解密失败:", error);
    throw new Error("解密过程中发生错误");
  }
}
//new Date().toLocaleString('sv-SE') // "2025-10-27 14:32:45"
//=================================================================================
//=================================================================================
//=================================================================================
/**
 * 获取指定时间日期
 * @param {str} 年月日
 */
function getTimes(t) {
    const now = new Date();
    // 获取年、月、日
    const year = now.getFullYear(); // 获取年份
    const month = now.getMonth() + 1; // 获取月份，注意月份是从0开始的
    const date = now.getDate(); // 获取日期
    
    // 获取星期
    const days = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
    const dayOfWeek = days[now.getDay()]; // 获取星期
    // 获取时、分、秒
    const hours = now.getHours(); // 获取小时
    const minutes = now.getMinutes(); // 获取分钟
    const seconds = now.getSeconds(); // 获取秒
    let m = ''
    if(t=='年'){
        m = year
    }else if(t=='月'){
        m = month
    }else if(t=='日'){
        m = date
    }else if(t=='时'){
        m = hours
    }else if(t=='分'){
        m = minutes
    }else if(t=='秒'){
        m = seconds
    }else if(t=='星期'){
        m = dayOfWeek
    }
    return m
}

/**
 * 计算距离「今天 h:m」还剩多少毫秒
 * @param {number} t 00:00
 * @param {number} h 0-23
 * @param {number} m 0-59
 * @returns {number} 剩余毫秒数
 */
function timeUntilNext(t) {
  let h, m;
  if (String(t).includes(':')) {
    [h, m] = String(t).split(':');
  } else {
    h = t;          // 只给了小时
    m = 0;          // 分钟默认为 00
  }
  const now = new Date();
  const target = new Date(now.getFullYear(), now.getMonth(), now.getDate(), Number(h), Number(m), 0, 0);
  if (now >= target) target.setDate(target.getDate() + 1);
  return target - now;
}

/*====================
const wx_code
//微信id
const wx_id
//微信appid
const wx_appid
//获取手机号加密地址
const wx_phone
//授权的Authorization值
const wx_token
//IP代理
const IP_agent
//协议类型
const wx_type
======================*/
function wechat(wxid, appid, url, d) {
    //console.log(url)
    //wx_url = url
    wx_id = wxid
    wx_appid = appid
    //判断鸡场接口
    //url = 'http://192.168.10.155:8011/'
    if(url.indexOf('/')==-1){
        wx_url = 'http://'+url+'/api/v1/wx/app/get/code'
        wx_phone = 'http://'+url+'/api/v1/wx/app/get/all/mobile'
        wx_cloud = 'http://'+url+'/api/v1/wx/app/call/function'
        wx_token = ''
        wx_type ='牛子'
    }else{
        if(url.indexOf('getMiniProgramCode')>=0){
            wx_url = 'http://'+url.replace('//','/').split('/')[1]+'/prod-api/wechat/api/getMiniProgramCode'
            wx_phone = 'http://'+url.replace('//','/').split('/')[1]+'/prod-api/wechat/api/getPhoneEncryptData'
            wx_token = process.env["soy_codetoken_data"]
            wx_cloud = 'http://'+url.replace('//','/').split('/')[1]+'/prod-api/wechat/api/cloudFunction'
            wx_type ='鸡场'
            
        }else{
            wx_url = 'http://'+url.replace('//','/').split('/')[1]+'/api/v1/wx/app/get/code'
            wx_phone = 'http://'+url.replace('//','/').split('/')[1]+'/api/v1/wx/app/get/all/mobile'
            wx_cloud = 'http://'+url.replace('//','/').split('/')[1]+'/api/v1/wx/app/call/function'
            wx_token = ''
            
            wx_type ='牛子'
        }
        
    }
    
    
    
    if(d){
        IP_agent = fetchProxyIp(d)
    }
    //console.log(wx_url)
    /*if(wx_id && wx_appid && wx_url){
        return true
    }else{
        return false
    }*/
}
async function get_wxcode() {
    let body = `{"wxid":"${wx_id}","appid":"${wx_appid}"}`
    let headers = {'Host': wx_url.replace('//','/').split('/')[1],'Content-Type': 'application/json',"Authorization": wx_token}
    let d = {
        url: wx_url,
        body:body,
        headers: headers,
        timeout: 30000,
    }
    //console.log(d)
    let {err, res, data} = await send('post', d, IP_agent)
    if(err){
        console.log('取code状态码:'+err+','+data?data:res)
    }else{
        //return data?data:null
        //console.log(data)
        if(data === null){
            console.log("取code：请求服务器返回空数据")
            return
        }
        if(data.code == 200 || data.Code == 0){
            let code = data.data ? data.data.code : data.Data.code
            return code
        }else{
            console.log(res)
            console.log(`取code：${data.msg?data.msg:data.Message}`)
            
            return
        }
    }
}
async function get_wxphone() {
    //console.log('微信')
    let body = `{"wxid":"${wx_id}","appid":"${wx_appid}","data":"phonecode"}`
    let headers = {'Host': wx_url.replace('//','/').split('/')[1],'Content-Type': 'application/json',"Authorization": wx_token}
    let d = {
        url: wx_phone,
        body:body,
        headers: headers,
        timeout: 30000,
    }
    //console.log(d)
    let {err, res, data} = await send('post', d, IP_agent)
    if(err){
        console.log('取手机号状态码:'+err+','+data?data:res)
    }else{
        //return data?data:null
        //console.log(JSON.stringify(data))
        if(data === null){
            console.log("取手机号：请求服务器返回空数据")
            return
        }
        if(data.code == 200 || data.Code == 0){
            let encrypted = ''
            let iv = ''
            let codep = ''
            let data_list = data.data ? data.data : data.Data
            
            if(wx_type =='牛子'){
                let p_data = JSON.parse(data_list.Data)
                //console.log(p_data)
                encrypted = p_data['wx_phone'].encryptedData
                iv = p_data['wx_phone'].iv
                codep = JSON.parse(p_data['wx_phone'].data).code
                //console.log(encrypted,iv,codep)
            }else{
                encrypted = data_list[0].encryptedData
                iv = data_list[0].iv
                codep = data_list[0].code
            }
            if(codep){
                return {codep,iv,encrypted}
                
            }else{
                console.log(`取手机号：截取手机号失败,可能未注册`)
                console.log(JSON.stringify(data))
                return
            }
            //console.log(codep)
            
        }else{
            console.log(`取手机号：${data.msg?data.msg:data.Message}`)
            return
        }
    }
}
async function get_wxfunction(n){
    //let body = `{"wxId":"${wx_id}","appId":"${wx_appid}","data":"{\"api_name\":\"${n}\",\"with_credentials\": true}"}`
    const body = {
        wxId: wx_id,
        appId: wx_appid,
        data: JSON.stringify({
            api_name: n,
            with_credentials: true,
            data: {lang:"zh_CN"}
        })
    }
    let headers = {'Host': wx_cloud.replace('//','/').split('/')[1],'Content-Type': 'application/json',"Authorization": wx_token}
    let d = {
        url: wx_cloud,
        body:body,
        headers: headers,
        timeout: 30000,
    }
    //console.log(d)
    let {err, res, data} = await send('post', d, IP_agent)
    if(err){
        console.log('云函数状态码:'+err+','+data?data:res)
    }else{
        //return data?data:null
        //console.log(JSON.stringify(data))
        if(data === null){
            console.log("云函数：请求服务器返回空数据")
            return
        }
        if(data.code == 200 || data.Code == 0){
            let code = data.data ? data.data.data : data.Data.data
            const decoded = JSON.parse(Buffer.from(code, 'base64').toString('utf8'));
            //console.log(decoded)
            return decoded
        }else{
            console.log(res)
            console.log(`云函数：${data.msg?data.msg:data.Message}`)
            
            return
        }
    }
}


function get_dev(s = os.cpus()[0].model, len = 16, upper = false) {
    if(len==10){
        return MD5(s,16,upper).slice(0, 10)
    }else{
        return MD5(s,16,upper)
    }
  
  
}
function up_URL(s) {
    if (typeof s !== 'string') {
        console.log('URL编码: 输入必须是字符串, 返回原数据');
        return s;
    } else {
        // 判断是否已经编码
        try {
            const decoded = decodeURIComponent(s);
            const reEncoded = encodeURIComponent(decoded);
            if (s === reEncoded) {
                //console.log('URL编码: 输入字符串可能已经被编码过');
                return s; // 返回原字符串
            } else {
                //console.log('URL编码: 输入字符串未被编码，进行编码');
                return encodeURIComponent(s); // 返回编码后的字符串
            }
        } catch (error) {
            // 如果解码失败，说明字符串可能已经被编码
            //console.log('URL编码: 输入字符串可能已经被编码过（解码失败）');
            return s; // 返回原字符串
        }
    }
}
function dow_URL(s) {
    if (typeof s !== 'string') {
        console.log('URL解码: 输入必须是字符串, 返回原数据');
        return s;
    } else {
        // 尝试解码字符串
        try {
            const decoded = decodeURIComponent(s);
            // 检查解码后的字符串是否与原始字符串一致
            if (decoded === s) {
                //console.log('URL解码: 输入字符串未被编码，无需解码');
                return s; // 返回原字符串
            } else {
                //console.log('URL解码: 输入字符串已被编码，进行解码');
                return decoded; // 返回解码后的字符串
            }
        } catch (error) {
            // 如果解码失败，说明字符串可能未被编码
            //console.log('URL解码: 输入字符串可能未被编码，返回原数据');
            return s; // 返回原字符串
        }
    }
}
async function detection_version() {
    try {
        await fs.access(localFile);
        api = require(localFile)
    } catch {
        console.log('检测api文件不存在,无法更新');
    }
    if(api){
        let net_d = await axios.get(REPO_URL, {responseType: 'text',timeout: 5000});
        let file = net_d.data;
        let net_version = file.match(/版本号:(\d+\.\d+\.\d+_\d)/)[1]//.toString()
        let version = await get_version().match(/版本号:(\d+\.\d+\.\d+_\d)/)[1]
        //console.error(net_version,version)
        if(net_version!==version){
            await up_version()
        }
        
    }
}
async function up_version() {
    console.log("[*] 正在下载 soy_api.js...");
    const { data } = await axios.get(REPO_URL, { responseType: "text" });
    if (!data.includes("module.exports")) {
        console.log("[*]下载的文件内容无效");
        
    }
    //await fs.writeFile(localFile, data, "utf8");
    //console.log("[*] 已写入 soy_api.js");
    (async () => {
        try {
            await fs.writeFile(localFile, data, 'utf8');
            console.log('[*] 已写入 soy_api.js');
        } catch (err) {
            console.error('[!] 写入失败:跳过更新');
        }
        
    })();
}
//================================================
function get_version() {
  return '版本号:25.12.07_1'
}
detection_version()
//================================================
module.exports = {
  微信: wechat,
  微信code: get_wxcode,
  微信手机:get_wxphone,
  微信云函数:get_wxfunction,
  版本号: get_version,
  延时: wait,
  延时_随机: Sleep_time,
  手机号: phoneNum,
  左右侧补位: padStr,
  数组_取随机值: randomList,
  CryptoJs: CryptoJS,
  字符串_随机: getStr,
  数字_随机: getNum,
  数字_取范围: getRandomInt,
  校验_HmacSHA256: HmacSHA256,
  校验_奇偶: checkOddEven,
  校验_md5: MD5,
  DES加密: DES_encryptBy,
  DES解密: DES_decryptBy,
  AES加密: AES_encrypt,
  AES解密: AES_decrypt,
  生成UUID: getUUID,
  字符串_生成句子: get_random_sentence,
  json_转换字符串: getStrFun,
  RSA加密: encrypt_rsa,
  RSA解密: decrypt_rsa,
  获取ip: fetchProxyIp,
  NodeRSA: NodeRSA,
  发送HTTP请求: send,
  时间_取时间戳: getTimestamps,
  时间_格式化: formatDateTime,
  时间_取日期: getTimes,
  时间_取时间差: timeUntilNext,
  DES_3加密: tripleDESEncrypt,
  DES_3解密: tripleDESDecrypt,
  取设备: get_dev,
  编码_url: up_URL,
  解密_url: dow_URL,
};